using UnityEngine;
using System.Collections;
using DanielSig.UnityExtensions;


/// <summary>
/// 64 Bit Unit Vector. Very efficient way of storing a unit vector with a precision error of
/// 2.24 millionth of a nano square degree. This data structure has very low noise ratio (less than 0.0000000001%),
/// as opposed to unit vectors in euclidean space using 3 single precision (3 * 32bits = 96bits)
/// floating point values (more than 99.99% noise). This data structure also has precisions much
/// more evenly distributed, as opposed to using eular angles where the poles have most precision.
/// This data structure is mainly designed for networking, where data efficiency is important,
/// but not the speed of computability. Use this structure for sending and storing unit vectors.
/// </summary>
namespace DanielSig.Geometry
{
	public struct UnitVector64 : I64Bit
	{
		
		public static implicit operator Vector3(UnitVector64 unitVector)
		{
			return unitVector.vector;
		}
		public static explicit operator UnitVector64(Vector3 vector)
		{
			return new UnitVector64(vector);
		}
		
		public UnitVector64(Vector3 vector)
		{
			_data = 0;
			this.vector = vector;
		}
		public UnitVector64(ulong data)
		{
			_data = data;
		}
	
		public Vector3 vector
		{
			get
			{
				double u = uValue;
				double v = vValue;
				bool back = (u + v) < 1F;
				if(!back)
				{
					double temp = u;
					u = 1F - v;
					v = 1F - temp;
				}
				Vector3 start = back ? Vector3.back : Vector3.forward;
				Vector3 uDir;
				Vector3 vDir;
				if((_data & UP_BIT) > 0)
				{
					if((_data & RIGHT_BIT) > 0)
					{
						uDir = back ? Vector3.up - Vector3.back : Vector3.right - Vector3.forward;
						vDir = back ? Vector3.right - Vector3.back : Vector3.up - Vector3.forward;
					}
					else
					{
						uDir = back ? Vector3.up - Vector3.back : Vector3.left - Vector3.forward;
						vDir = back ? Vector3.left - Vector3.back : Vector3.up - Vector3.forward;
					}
				}
				else if((_data & RIGHT_BIT) > 0)
				{
					uDir = back ? Vector3.down - Vector3.back : Vector3.right - Vector3.forward;
					vDir = back ? Vector3.right - Vector3.back : Vector3.down - Vector3.forward;
				}
				else
				{
					uDir = back ? Vector3.down - Vector3.back : Vector3.left - Vector3.forward;
					vDir = back ? Vector3.left - Vector3.back : Vector3.down - Vector3.forward;
				}
				return (start + uDir * (float)u + vDir * (float)v).normalized;
			}
			set
			{
				value.Normalize();
				bool back = value.z < 0F;
				Vector3 start = back ? Vector3.back : Vector3.forward;
				Vector3 uDir;
				Vector3 vDir;
				_data = 0;
				if(value.y > 0F)
				{
					_data |= UP_BIT;
					if(value.x > 0F)
					{
						_data |= RIGHT_BIT;
						uDir = back ? Vector3.up - Vector3.back : Vector3.up - Vector3.forward;
						vDir = back ? Vector3.right - Vector3.back : Vector3.right - Vector3.forward;
					}
					else
					{
						uDir = back ? Vector3.up - Vector3.back : Vector3.up - Vector3.forward;
						vDir = back ? Vector3.left - Vector3.back : Vector3.left - Vector3.forward;
					}
				}
				else if(value.x > 0F)
				{
					_data |= RIGHT_BIT;
					uDir = back ? Vector3.down - Vector3.back : Vector3.down - Vector3.forward;
					vDir = back ? Vector3.right - Vector3.back : Vector3.right - Vector3.forward;
				}
				else
				{
					uDir = back ? Vector3.down - Vector3.back : Vector3.down - Vector3.forward;
					vDir = back ? Vector3.left - Vector3.back : Vector3.left - Vector3.forward;
				}
				double uLen = 1D / uDir.magnitude;
				double vLen = 1D / vDir.magnitude;
				Vector3 uNormal = uDir * (float)uLen;
				Vector3 vNormal = vDir * (float)vLen;
				Plane p = new Plane(uNormal.Cross(vNormal).normalized, start);
				Vector3 flat = p.CutLine(Vector3.zero, value*5F);
				
				double v = Mathf.Sqrt(flat.x * flat.x * 2) * vLen;
				Vector3 r = flat - vDir * (float)v;
				double u = (r - start).magnitude * vLen;
				
				if(!back)
				{
					u = 1F - u;
					v = 1F - v;
				}
				
				_data |= (ulong)(((ulong)(u * UV_MAX + 0.5D) & UV_MAX) << 31);
				_data |= (ulong)((ulong)(v * UV_MAX + 0.5D) & UV_MAX);
			}
		}
		
		private double uValue
		{
			get
			{
				return ((_data >> 31) & UV_MAX) * INV_UV_MAX;
			}
			set
			{
				_data = (ulong)(_data & (~U_VALUE));
				_data |= (ulong)(((ulong)(value * UV_MAX + 0.5F) << 31) & U_VALUE);
			}
		}
		private double vValue
		{
			get
			{
				return (_data & UV_MAX) * INV_UV_MAX;
			}
			set
			{
				_data = (ulong)(_data & (~V_VALUE));
				_data |= (ulong)((ulong)(value * UV_MAX + 0.5F) & V_VALUE);
			}
		}
	
		public ulong value
		{
			get
			{
				return _data;
			}
			set
			{
				_data = value;
			}
		}
		public ulong rawData
		{
			get
			{
				return _data;
			}
			set
			{
				_data = value;
			}
		}
		
		private ulong _data;
		
		/*
		1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
		0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
		0011 1111 1111 1111 1111 1111 1111 1111 1000 0000 0000 0000 0000 0000 0000 0000
		0000 0000 0000 0000 0000 0000 0000 0000 0111 1111 1111 1111 1111 1111 1111 1111
		*/
		private const ulong UP_BIT = 	0x8000000000000000;
		private const ulong RIGHT_BIT = 0x4000000000000000;
		private const ulong U_VALUE = 	0x3FFFFFFF80000000;
		private const ulong V_VALUE = 	0x000000007FFFFFFF;
		private const ulong UV_MAX = V_VALUE;
		private const double INV_UV_MAX = 1D / UV_MAX;
	}
}